<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>计算假想球</title>
  <style>
    #canvas {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      z-index: -1;
    }

    .block-text {
      width: 50px;
      display: inline-block;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  <div>
    <span class="block-text"> 0颗球</span>
    <input type="range" min="0" max="1" value="0.5" id="positionRange" step="0.01" />
    1颗球
  </div>
  <p>
    从袋口到目标球连线，获得 <font color="red">A</font> 点。<br />
    在正视图下，获得最边上的 <font color="green">B</font> 点。<br />
    以<font color="red">A</font> 点为中心点，镜像 <font color="green">B</font> 点，获得 <font color="blue">C</font> 点。<br />
    <font color="blue">C</font> 点则为假想球最边的位置，也就是母球击打边的位置。<br />
    <br />
    掌握此方法，中杆方式击打，100%进球。
  </p>
  
    <a
      href="https://github.com/lecepin/billiard-aim-calculation"
      target="_blank"
      class="github-corner"
      aria-label="View source on GitHub"
      ><svg
        width="80"
        height="80"
        viewBox="0 0 250 250"
        style="
          fill: #151513;
          color: #fff;
          position: absolute;
          top: 0;
          border: 0;
          right: 0;
        "
        aria-hidden="true"
      >
        <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
        <path
          d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
          fill="currentColor"
          style="transform-origin: 130px 106px"
          class="octo-arm"
        ></path>
        <path
          d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
          fill="currentColor"
          class="octo-body"
        ></path></svg></a
    ><style>
      .github-corner:hover .octo-arm {
        animation: octocat-wave 560ms ease-in-out;
      }
      @keyframes octocat-wave {
        0%,
        100% {
          transform: rotate(0);
        }
        20%,
        60% {
          transform: rotate(-25deg);
        }
        40%,
        80% {
          transform: rotate(10deg);
        }
      }
      @media (max-width: 500px) {
        .github-corner:hover .octo-arm {
          animation: none;
        }
        .github-corner .octo-arm {
          animation: octocat-wave 560ms ease-in-out;
        }
      }
    </style>  
  
  <script>
    const cnv = document.getElementById('canvas');
    const positionRange = document.getElementById('positionRange');
    const ctx = cnv.getContext('2d');
    const dpr = window.devicePixelRatio;
    const width = window.innerWidth;
    const height = window.innerHeight;
    const r = Math.min(width, height) / 9 / 2;
    const paddingText = 10;
    const cueBallPos = {
      x: width / 2,
      y: height / 2 + 3 * 2 * r
    }
    const targetBallPos = {
      x: width / 2,
      y: height / 2,
    }
    // 计算球
    const shadowBallPos = {
      x: width / 2,
      y: targetBallPos.y + 2 * r,
    }
    const predictTargetBallPos = {
      x: width / 2,
      y: targetBallPos.y - 2 * 2 * r,
    }
    const angleDisplayPos = {
      x: shadowBallPos.x,
      y: shadowBallPos.y,
      text: 0
    }
    const frontViewTargetBallPos = {
      x: width / 2 + 3 * 2 * r,
      y: height / 2,
    }
    const frontViewShadowBallPos = {
      x: width / 2 + 3 * 2 * r,
      y: height / 2,
    }


    cnv.style.width = width + 'px';
    cnv.style.height = height + 'px';
    cnv.width = width * dpr;
    cnv.height = height * dpr;
    ctx.scale(dpr, dpr);
    ctx.font = '14px sans-serif';
    ctx.textAlign = 'right';
    ctx.textBaseline = "middle"
    ctx.lineWidth = 2;

    rangeInput({
      target: positionRange
    })
    positionRange.addEventListener('input', rangeInput)


    function rangeInput(e) {
      const shandowBallY = Math.sqrt(Math.pow(2 * r, 2) - Math.pow(e.target.value * 2 * r, 2));
      const sinAngle = shandowBallY / (2 * r);
      const cosAngle = e.target.value * 2 * r / (2 * r)

      shadowBallPos.x = cueBallPos.x = width / 2 + e.target.value * 2 * r;
      shadowBallPos.y = targetBallPos.y + shandowBallY;

      angleDisplayPos.x = shadowBallPos.x + r * cosAngle;
      angleDisplayPos.y = shadowBallPos.y + r * sinAngle;
      angleDisplayPos.text = ~~(Math.asin(cosAngle) * 180 / Math.PI);

      predictTargetBallPos.y = targetBallPos.y - 2 * 2 * r * sinAngle;
      predictTargetBallPos.x = targetBallPos.x - 2 * 2 * r * cosAngle;

      frontViewShadowBallPos.x = width / 2 + 3 * 2 * r + e.target.value * 2 * r;

      render();
    }

    function render() {
      ctx.clearRect(0, 0, width, height);

      // 目标球
      ctx.restore();
      ctx.save();
      ctx.beginPath();
      ctx.strokeStyle = '#000';
      ctx.arc(targetBallPos.x, targetBallPos.y, r, 0, Math.PI * 2);
      ctx.strokeStyle = 'green';
      ctx.stroke();
      ctx.fillText('目标球', targetBallPos.x - r - paddingText, targetBallPos.y);
      ctx.beginPath();
      ctx.arc(targetBallPos.x, targetBallPos.y, 2, 0, Math.PI * 2);
      ctx.fill();

      // 目标球结果
      ctx.restore();
      ctx.save();
      ctx.beginPath();
      ctx.strokeStyle = 'blue';
      ctx.arc(predictTargetBallPos.x, predictTargetBallPos.y, r, 0, Math.PI * 2);
      ctx.stroke();
      ctx.fillText('结果', predictTargetBallPos.x - r - paddingText, predictTargetBallPos.y);
      ctx.beginPath();
      ctx.arc(predictTargetBallPos.x, predictTargetBallPos.y, 2, 0, Math.PI * 2);
      ctx.fill();

      // 母球
      ctx.restore();
      ctx.save();
      ctx.beginPath();
      ctx.strokeStyle = '#000';
      ctx.arc(cueBallPos.x, cueBallPos.y, r, 0, Math.PI * 2);
      ctx.stroke();
      ctx.fillText('母球', cueBallPos.x - r - paddingText, cueBallPos.y);
      ctx.beginPath();
      ctx.arc(cueBallPos.x, cueBallPos.y, 2, 0, Math.PI * 2);
      ctx.fill();

      // 影子球
      ctx.restore();
      ctx.save();
      ctx.beginPath();
      ctx.strokeStyle = '#000';
      ctx.arc(shadowBallPos.x, shadowBallPos.y, r, 0, Math.PI * 2);
      ctx.setLineDash([2, 3]);
      ctx.stroke();
      ctx.fillText('假想球', shadowBallPos.x - r - paddingText, shadowBallPos.y);
      ctx.beginPath();
      ctx.arc(shadowBallPos.x, shadowBallPos.y, 2, 0, Math.PI * 2);
      ctx.fill();

      // 两球连线
      ctx.restore();
      ctx.save();
      // 母球 -> 影子球
      ctx.beginPath();
      ctx.strokeStyle = 'black';
      ctx.moveTo(cueBallPos.x, cueBallPos.y);
      ctx.lineTo(shadowBallPos.x, targetBallPos.y);
      ctx.stroke();
      // 目标球 -> 影子球
      ctx.beginPath();
      ctx.strokeStyle = 'green';
      ctx.moveTo(targetBallPos.x, targetBallPos.y);
      ctx.lineTo(angleDisplayPos.x, angleDisplayPos.y);
      ctx.stroke();
      ctx.textAlign = 'left';
      ctx.fillText(angleDisplayPos.text + '°', angleDisplayPos.x + paddingText, angleDisplayPos.y);
      // A
      ctx.beginPath();
      ctx.arc(targetBallPos.x + (shadowBallPos.x - targetBallPos.x) / 2, targetBallPos.y + (shadowBallPos.y - targetBallPos.y) / 2, 4, 0, Math.PI * 2);
      ctx.fillStyle = 'red';
      ctx.fill();
      ctx.font = '18px sans-serif';
      ctx.fillText('A', targetBallPos.x + (shadowBallPos.x - targetBallPos.x) / 2 + paddingText, targetBallPos.y + (shadowBallPos.y - targetBallPos.y) / 2);
      // B
      ctx.beginPath();
      ctx.arc(targetBallPos.x + r, targetBallPos.y, 4, 0, Math.PI * 2);
      ctx.fillStyle = 'green';
      ctx.fill();
      ctx.fillText('B', targetBallPos.x + r + paddingText, targetBallPos.y);
      // C
      ctx.beginPath();
      ctx.textAlign = 'right';
      ctx.arc(shadowBallPos.x - r, targetBallPos.y, 4, 0, Math.PI * 2);
      ctx.fillStyle = 'blue';
      ctx.fill();
      ctx.fillText('C', shadowBallPos.x - r - paddingText, targetBallPos.y);

      // 目标球 -> 结果
      ctx.beginPath();
      ctx.strokeStyle = 'blue';
      ctx.moveTo(targetBallPos.x, targetBallPos.y);
      ctx.lineTo(predictTargetBallPos.x, predictTargetBallPos.y);
      ctx.stroke();

      // 正视图
      ctx.restore();
      ctx.save();
      // 目标球
      ctx.beginPath();
      ctx.strokeStyle = 'green';
      ctx.arc(frontViewTargetBallPos.x, frontViewTargetBallPos.y, r, 0, Math.PI * 2);
      ctx.stroke();
      // 影子球
      ctx.beginPath();
      ctx.strokeStyle = '#000';
      ctx.arc(frontViewShadowBallPos.x, frontViewShadowBallPos.y, r, 0, Math.PI * 2);
      ctx.setLineDash([2, 3]);
      ctx.stroke();
      // 点
      ctx.beginPath();
      ctx.arc(frontViewTargetBallPos.x + (frontViewShadowBallPos.x - frontViewTargetBallPos.x) / 2, frontViewTargetBallPos.y + (frontViewShadowBallPos.y - frontViewTargetBallPos.y) / 2, 4, 0, Math.PI * 2);
      ctx.fillStyle = 'red';
      ctx.fill();
      ctx.font = '18px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('A', frontViewTargetBallPos.x + (frontViewShadowBallPos.x - frontViewTargetBallPos.x) / 2, frontViewTargetBallPos.y + (frontViewShadowBallPos.y - frontViewTargetBallPos.y) / 2 + paddingText);
      ctx.beginPath();
      ctx.arc(frontViewTargetBallPos.x + r, frontViewTargetBallPos.y, 4, 0, Math.PI * 2);
      ctx.fillStyle = 'green';
      ctx.fill();
      ctx.fillText('B', frontViewTargetBallPos.x + r, frontViewTargetBallPos.y + paddingText);
      ctx.beginPath();
      ctx.arc(frontViewShadowBallPos.x - r, frontViewShadowBallPos.y, 4, 0, Math.PI * 2);
      ctx.fillStyle = 'blue';
      ctx.fill();
      ctx.fillText('C', frontViewShadowBallPos.x - r, frontViewShadowBallPos.y + paddingText);
      // 文字
      ctx.fillStyle = 'black';
      ctx.fill();
      ctx.font = 'bold 16px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('正视图', frontViewTargetBallPos.x + (frontViewShadowBallPos.x - frontViewTargetBallPos.x) / 2, frontViewTargetBallPos.y + 2 * r);
      ctx.fillText('俯视图', targetBallPos.x, cueBallPos.y + 2 * r);

    }
  </script>
</body>

</html>
